// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/renderer_main_platform_delegate.h"

#include "base/android/build_info.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "sandbox/sandbox_features.h"

#if BUILDFLAG(USE_SECCOMP_BPF)
#include "content/common/sandbox_linux/android/sandbox_bpf_base_policy_android.h"
#include "content/public/common/content_features.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
#endif

namespace content {

namespace {

    // Scoper class to record a SeccompSandboxStatus UMA value.
    class RecordSeccompStatus {
    public:
        enum SeccompSandboxStatus {
            NOT_SUPPORTED = 0, // Seccomp is not supported.
            DETECTION_FAILED, // Run-time detection of Seccomp+TSYNC failed.
            FEATURE_DISABLED, // Sandbox was disabled by FeatureList.
            FEATURE_ENABLED, // Sandbox was enabled by FeatureList.
            ENGAGED, // Sandbox was enabled and successfully turned on.
            STATUS_MAX
            // This enum is used by an UMA histogram, so only append values.
        };

        RecordSeccompStatus()
            : status_(NOT_SUPPORTED)
        {
        }

        ~RecordSeccompStatus()
        {
            UMA_HISTOGRAM_ENUMERATION("Android.SeccompStatus.RendererSandbox", status_,
                STATUS_MAX);
        }

        void set_status(SeccompSandboxStatus status) { status_ = status; }

    private:
        SeccompSandboxStatus status_;
        DISALLOW_COPY_AND_ASSIGN(RecordSeccompStatus);
    };

#if BUILDFLAG(USE_SECCOMP_BPF)
    // Determines if the running device should support Seccomp, based on the Android
    // SDK version.
    bool IsSeccompBPFSupportedBySDK()
    {
        auto* info = base::android::BuildInfo::GetInstance();
        if (info->sdk_int() < 22) {
            // Seccomp was never available pre-Lollipop.
            return false;
        } else if (info->sdk_int() == 22) {
            // On Lollipop-MR1, only select Nexus devices have Seccomp available.
            const char* const kDevices[] = {
                "deb",
                "flo",
                "hammerhead",
                "mako",
                "manta",
                "shamu",
                "sprout",
                "volantis",
            };

            for (auto* device : kDevices) {
                if (strcmp(device, info->device()) == 0) {
                    return true;
                }
            }
        } else {
            // On Marshmallow and higher, Seccomp is required by CTS.
            return true;
        }
        return false;
    }
#endif // USE_SECCOMP_BPF

} // namespace

RendererMainPlatformDelegate::RendererMainPlatformDelegate(
    const MainFunctionParams& parameters) { }

RendererMainPlatformDelegate::~RendererMainPlatformDelegate()
{
}

void RendererMainPlatformDelegate::PlatformInitialize()
{
}

void RendererMainPlatformDelegate::PlatformUninitialize()
{
}

bool RendererMainPlatformDelegate::EnableSandbox()
{
    RecordSeccompStatus status_uma;

#if BUILDFLAG(USE_SECCOMP_BPF)
    // Determine if Seccomp is available via the Android SDK version.
    if (!IsSeccompBPFSupportedBySDK())
        return true;

    // Do run-time detection to ensure that support is present.
    if (!sandbox::SandboxBPF::SupportsSeccompSandbox(
            sandbox::SandboxBPF::SeccompLevel::MULTI_THREADED)) {
        status_uma.set_status(RecordSeccompStatus::DETECTION_FAILED);
        LOG(WARNING) << "Seccomp support should be present, but detection "
                     << "failed. Continuing without Seccomp-BPF.";
        return true;
    }

    // Seccomp has been detected, check if the field trial experiment should run.
    if (base::FeatureList::IsEnabled(features::kSeccompSandboxAndroid)) {
        status_uma.set_status(RecordSeccompStatus::FEATURE_ENABLED);

        sandbox::SandboxBPF sandbox(new SandboxBPFBasePolicyAndroid());
        CHECK(sandbox.StartSandbox(
            sandbox::SandboxBPF::SeccompLevel::MULTI_THREADED));

        status_uma.set_status(RecordSeccompStatus::ENGAGED);
    } else {
        status_uma.set_status(RecordSeccompStatus::FEATURE_DISABLED);
    }
#endif
    return true;
}

} // namespace content
